package org.alwayssuper.protocol.codec;

import org.alwayssuper.protostar.Schema;
import org.alwayssuper.protostar.SchemaManager;
import org.alwayssuper.protostar.schema.RuntimeSchema;
import org.alwayssuper.protostar.util.ArrayMap;
import org.alwayssuper.protostar.util.Explain;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufAllocator;
import io.netty.buffer.PooledByteBufAllocator;
import org.alwayssuper.protocol.basics.GBTMessage;
import org.alwayssuper.protocol.commons.GBTRequestType;
import org.alwayssuper.protocol.commons.GBTResponseType;
import org.alwayssuper.protocol.commons.JTUtils;
import org.alwayssuper.protocol.commons.VersionType;

/**
 * GBT协议编码器
 * @author alwaysSuper
 */
public class GBTMessageEncoder {

    private static final ByteBufAllocator ALLOC = PooledByteBufAllocator.DEFAULT;

    private final SchemaManager schemaManager;

    private final ArrayMap<RuntimeSchema> headerSchemaMap;

    public GBTMessageEncoder(String... basePackages) {
        this.schemaManager = new SchemaManager(basePackages);
        this.headerSchemaMap = schemaManager.getRuntimeSchema(GBTMessage.class);
    }

    public GBTMessageEncoder(SchemaManager schemaManager) {
        this.schemaManager = schemaManager;
        this.headerSchemaMap = schemaManager.getRuntimeSchema(GBTMessage.class);
    }

    public ByteBuf encode(GBTMessage message) {
        return encode(message, null);
    }

    public ByteBuf encode(GBTMessage message, Explain explain) {
        int version = message.getProtocolVersion();
        int headLength = JTUtils.headerLength(version);
        int bodyLength = 0;

        Schema headSchema = headerSchemaMap.get(version);
        Schema bodySchema;

        //处理32960协议消息应答时，下发应答的消息类型和服务器收到的需被应答的消息的消息类型相同的问题
        if(message.getResponseType() != GBTResponseType.命令 ){
            bodySchema = schemaManager.getRuntimeSchema(GBTRequestType.应答标识, version);
        }else {
            bodySchema = schemaManager.getRuntimeSchema(message.getMessageId(), version);
        }

        // 在初始化时就预留空间用于头部标识符  先写报文体
        ByteBuf output;
        if (bodySchema != null) {
            output = ALLOC.buffer(headLength + bodySchema.length() + 2); // +2 用于头部标识符
            output.writerIndex(headLength + 2); // 跳过头部标识符空间
            bodySchema.writeTo(output, message, explain);
            bodyLength = output.writerIndex() - headLength - 2;
            message.setPayloadLength(bodyLength);
        } else {
            //TODO:后续推敲一下这段的逻辑
            output = ALLOC.buffer(headLength + 2, 50 + 2); // +2 用于头部标识符
            output.writerIndex(headLength + 2); // 跳过头部标识符空间
        }

        int writerIndex = output.writerIndex();
        if (writerIndex > 0) {
            output.writerIndex(0);
            // 在头部写入标识符
            VersionType versionType = VersionType.values()[version];
            output.writeShort(versionType.getHeaderIdentifier());
            // 写入头部信息
            headSchema.writeTo(output, message, explain);
            output.writerIndex(writerIndex);
        } else {
            // 在头部写入标识符
            VersionType versionType = VersionType.values()[version];
            output.writeShort(versionType.getHeaderIdentifier());
            // 写入头部信息
            headSchema.writeTo(output, message, explain);
        }

        output = sign(output);
        
        return output;
    }

    /** 签名 */
    public static ByteBuf sign(ByteBuf buf) {
        byte checkCode = JTUtils.bcc(buf, 2,0);
        buf.writeByte(checkCode);
        return buf;
    }

}
